package zack.project.infrastructure.persistent.repository;

import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBlockingQueue;
import org.redisson.api.RDelayedQueue;
import org.redisson.api.RMap;
import org.springframework.stereotype.Repository;
import zack.project.domain.strategy.adapter.repository.IStrategyRepository;
import zack.project.domain.strategy.model.entity.StrategyAwardEntity;
import zack.project.domain.strategy.model.entity.StrategyEntity;
import zack.project.domain.strategy.model.entity.StrategyRuleEntity;
import zack.project.domain.strategy.model.valobj.*;
import zack.project.domain.strategy.service.rule.chain.factory.DefaultChainFactory;
import zack.project.infrastructure.persistent.dao.*;
import zack.project.infrastructure.persistent.po.*;
import zack.project.infrastructure.persistent.redis.RedissonService;
import zack.project.types.common.Constants;
import zack.project.types.exception.AppException;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static zack.project.types.enums.ResponseCode.UN_ASSEMBLED_STRATEGY_ARMORY;

@Slf4j
@Repository
public class StrategyRepository implements IStrategyRepository {
    @Resource
    private IStrategyAwardDao strategyAwardDao;
    @Resource
    private RedissonService redissonService;
    @Resource
    private IStrategyDao strategyDao;
    @Resource
    private IStrategyRuleDao strategyRuleDao;
    @Resource
    private IRuleTreeDao ruleTreeDao;
    @Resource
    private IRuleTreeNodeDao ruleTreeNodeDao;
    @Resource
    private IRuleTreeNodeLineDao ruleTreeNodeLineDao;
    @Resource
    private IRaffleActivityDao raffleActivityDao;
    @Resource
    private IRaffleActivityAccountDayDao raffleActivityAccountDayDao;
    @Resource
    private IRaffleActivityAccountDao raffleActivityAccountDao;

    //查询属于strategyId的奖品列表
    // 先在redis缓存中查再从数据库查
    @Override
    public List<StrategyAwardEntity> queryStrategyAwardList(Long strategyId) {
        //预热strategyId的奖品列表
        String cacheKey = Constants.RedisKey.STRATEGY_AWARD_LIST_KEY + strategyId;

        List<StrategyAwardEntity> strategyAwardEntityList = redissonService.getValue(cacheKey);
        if (null != strategyAwardEntityList && !strategyAwardEntityList.isEmpty()) {
            return strategyAwardEntityList;
        }
        //查询数据库表{strategy_award}中属于strategyId 的 StrategyAward列表
        List<StrategyAward> strategyAwards = strategyAwardDao.queryStrategyAwardListByStrategyId(strategyId);

        strategyAwardEntityList = new ArrayList<>(strategyAwards.size());
        for (StrategyAward strategyAward : strategyAwards) {
            StrategyAwardEntity strategyAwardEntity = StrategyAwardEntity.builder()
                    .strategyId(strategyAward.getStrategyId())
                    .awardId(strategyAward.getAwardId())
                    .awardCount(strategyAward.getAwardCount())
                    .awardCountSurplus(strategyAward.getAwardCountSurplus())
                    .awardRate(strategyAward.getAwardRate())
                    .awardSubtitle(strategyAward.getAwardSubtitle())
                    .awardTitle(strategyAward.getAwardTitle())
                    .sort(strategyAward.getSort())
                    .ruleModels(strategyAward.getRuleModels())
                    .build();
            strategyAwardEntityList.add(strategyAwardEntity);
        }
        //存入缓存
        redissonService.setValue(cacheKey, strategyAwardEntityList);
        return strategyAwardEntityList;
    }

    /**
     * 存储总坑位树和抽奖的概率配置
     * @param key
     * @param rateRange
     * @param strategyAwardSearchRateTable
     */
    @Override
    public void storeStrategyAwardSearchRateTable(String key, Integer rateRange, Map<Integer, Integer> strategyAwardSearchRateTable) {
        //往redis里存raterange方便生成一个在raterange范围内的随机数，
        // 当raterange=1000时，就生成0-1000以内的随机数进行抽奖
        //如果是分布式集群，别的节点可以读取raterange进行抽奖操作
        redissonService.setValue(Constants.RedisKey.STRATEGY_RATE_RANGE_KEY + key, rateRange);
        RMap<Integer, Integer> map =
                redissonService.getMap(Constants.RedisKey.STRATEGY_RATE_TABLE_KEY + key);
        map.putAll(strategyAwardSearchRateTable);

    }

    @Override
    public int getRateRange(Long strategyId) {
        return getRateRange(String.valueOf(strategyId));
    }

    @Override
    public int getRateRange(String key) {
        String cacheKey = Constants.RedisKey.STRATEGY_RATE_RANGE_KEY + key;
        if (!redissonService.isExists(cacheKey)) {
            throw new AppException(UN_ASSEMBLED_STRATEGY_ARMORY.getCode(), cacheKey + Constants.COLON + UN_ASSEMBLED_STRATEGY_ARMORY.getInfo());
        }
        return redissonService.getValue(cacheKey);
    }


    @Override
    public Integer getStrategyAwardAssemble(String key, Integer random) {
        return redissonService.getFromMap(Constants.RedisKey.STRATEGY_RATE_TABLE_KEY + key, random);
    }


    @Override
    public StrategyEntity queryStrategyEntityByStrategyId(Long strategyId) {

        String cacheKey = Constants.RedisKey.STRATEGY_KEY + strategyId;
        StrategyEntity strategyEntity = redissonService.getValue(cacheKey);
        if (strategyEntity != null) {
            return strategyEntity;
        }
        Strategy strategy = strategyDao.queryStrategyByStrategyId(strategyId);
        if (strategy == null) {
            return StrategyEntity.builder().build();
        }
        strategyEntity = StrategyEntity.builder()
                .strategyId(strategy.getStrategyId())
                .strategyDesc(strategy.getStrategyDesc())
                .ruleModels(strategy.getRuleModels())
                .build();
        redissonService.setValue(cacheKey, strategyEntity);
        return strategyEntity;
    }

    @Override
    public StrategyRuleEntity queryStrategyRule(Long strategyId, String ruleModel) {
        StrategyRule strategyRuleReq = new StrategyRule();
        strategyRuleReq.setStrategyId(strategyId);
        strategyRuleReq.setRuleModel(ruleModel);
        StrategyRule strategyRuleRes = strategyRuleDao.queryStrategyRule(strategyRuleReq);
        return StrategyRuleEntity.builder()
                .strategyId(strategyRuleRes.getStrategyId())
                .awardId(strategyRuleRes.getAwardId())
                .ruleType(strategyRuleRes.getRuleType())
                .ruleModel(strategyRuleRes.getRuleModel())
                .ruleValue(strategyRuleRes.getRuleValue())
                .ruleDesc(strategyRuleRes.getRuleDesc())
                .build();
    }

    @Override
    public String queryStrategyRuleValue(Long strategyId, Integer awardId, String ruleModel) {
        StrategyRule strategyRule = new StrategyRule();
        strategyRule.setStrategyId(strategyId);
        strategyRule.setAwardId(awardId);
        strategyRule.setRuleModel(ruleModel);
        String s = strategyRuleDao.queryStrategyRuleValue(strategyRule);
        return s;
    }

    @Override
    public String queryStrategyRuleValue(Long strategyId, String ruleModel) {
        return queryStrategyRuleValue(strategyId, null, ruleModel);
    }

    @Override
    public StrategyAwardRuleModelVO queryStrategyAwardRuleModelVO(Long strategyId, Integer randomAwardId) {
        StrategyAward strategyAward = new StrategyAward();
        strategyAward.setStrategyId(strategyId);
        strategyAward.setAwardId(randomAwardId);
        String ruleModel = strategyAwardDao.queryStrategyAwardRuleModels(strategyAward);
        if (ruleModel == null) {
            return null;
        }
        return StrategyAwardRuleModelVO.builder().ruleModel(ruleModel).build();
    }

    /**
     * 根据在数据库表{strategy_award}查找到的rule_models(决策树id)去配置对应的决策时
     * @param treeId
     * @return
     */
    @Override
    public RuleTreeVO queryRuleTreeVOByTreeId(String treeId) {
        //先在缓存中查找是否有已经装配好的决策树
        String cacheKey = Constants.RedisKey.RULE_TREE_VO_KEY + treeId;
        RuleTreeVO ruleTreeVOCache = redissonService.getValue(cacheKey);
        if (ruleTreeVOCache != null) {
            return ruleTreeVOCache;
        }
        //在数据库表{rule_tree}中查找tree配置
        // ,如{treeId:tree_luck_award treeName:规则树-兜底奖励	treeDesc规则树-兜底奖励 treeNodeRuleKey:rule_stock}
        RuleTree ruleTree = ruleTreeDao.queryRuleTreeByTreeId(treeId);
        //根据treeId在数据库表{rule_tree_node}中查找该id对应的决策树下的所有节点
        List<RuleTreeNode> ruleTreeNodes = ruleTreeNodeDao.queryRuleTreeNodeListByTreeId(treeId);
        //根据treeId在数据库表{rule_tree_node_line}中查找节点的连接关系和放行与否
        List<RuleTreeNodeLine> ruleTreeNodeLines = ruleTreeNodeLineDao.
                queryRuleTreeNodeLineListByTreeId(treeId);

        //构建(from节点名,List<RuleTreeNodeLineVO>)的map，
        // map的一条链表上，key是from节点名，value是(from节点名,to节点名，···）的list
        Map<String, List<RuleTreeNodeLineVO>> ruleTreeNodeLineVOHashMap = new HashMap<>();
        for (RuleTreeNodeLine ruleTreeNodeLine : ruleTreeNodeLines) {
            RuleTreeNodeLineVO ruleTreeNodeLineVO = RuleTreeNodeLineVO.builder()
                    .treeId(ruleTreeNodeLine.getTreeId())
                    .ruleNodeFrom(ruleTreeNodeLine.getRuleNodeFrom())
                    .ruleNodeTo(ruleTreeNodeLine.getRuleNodeTo())
                    .ruleLimitType(RuleLimitTypeVO.valueOf(ruleTreeNodeLine.getRuleLimitType()))
                    .ruleLimitValue(RuleLogicCheckTypeVO.valueOf(ruleTreeNodeLine.getRuleLimitValue()))
                    .build();
           ruleTreeNodeLineVOHashMap.computeIfAbsent(ruleTreeNodeLine.getRuleNodeFrom(),
                   k -> new ArrayList<RuleTreeNodeLineVO>()).add(ruleTreeNodeLineVO);

        }
        //构建节点表
        Map<String, RuleTreeNodeVO> ruleTreeNodeVOHashMap = new HashMap<String, RuleTreeNodeVO>();
        for (RuleTreeNode ruleTreeNode : ruleTreeNodes) {
            RuleTreeNodeVO ruleTreeNodeVO = RuleTreeNodeVO.builder()
                    .treeId(ruleTreeNode.getTreeId())
                    .ruleKey(ruleTreeNode.getRuleKey())
                    .ruleDesc(ruleTreeNode.getRuleDesc())
                    .ruleValue(ruleTreeNode.getRuleValue())
                    .treeNodeLineVOList(ruleTreeNodeLineVOHashMap.get(ruleTreeNode.getRuleKey()))
                    .build();
            ruleTreeNodeVOHashMap.put(ruleTreeNode.getRuleKey(), ruleTreeNodeVO);
        }

        RuleTreeVO resultRuleTreeVO = RuleTreeVO.builder()
                .treeId(treeId)
                .treeDesc(ruleTree.getTreeDesc())
                .treeName(ruleTree.getTreeName())
                .treeRootRuleNode(ruleTree.getTreeRootRuleKey())
                .treeNodeMap(ruleTreeNodeVOHashMap)
                .build();

        redissonService.setValue(cacheKey, resultRuleTreeVO);
        return resultRuleTreeVO;
    }


    @Override
    public void cacheStrategyAwardCount(String cacheKey, Integer awardCount) {
        if (redissonService.isExists(cacheKey)) {
            return;
        }
        redissonService.setAtomicLong(cacheKey, awardCount);
    }

    @Override
    public boolean subtractionAwardStock(String cacheKey) {
        return subtractionAwardStock(cacheKey, null);
    }

    @Override
    public Boolean subtractionAwardStock(String cacheKey, Date endDateTime) {
        long surplus = redissonService.decr(cacheKey);
        if (surplus < 0) {
            redissonService.setAtomicLong(cacheKey, 0);
            return false;
        }

        String lockKey = cacheKey + Constants.UNDERLINE + surplus;
        boolean lock = false;
        if (endDateTime != null) {
            long expireMillis = endDateTime.getTime() -
                    System.currentTimeMillis() +
                    TimeUnit.DAYS.toMillis(1);

            lock = redissonService.setNx(lockKey, expireMillis, TimeUnit.MILLISECONDS);
        } else {
            lock = redissonService.setNx(lockKey);
        }
        if (!lock) {
            log.info("策略奖品库存加锁失败 {}", lockKey);

        }
        return lock;

    }

    @Override
    public void awardStockConsumeSendQueue(StrategyAwardStockKeyVO strategyAwardStockKeyVO) {

        String cacheKey = Constants.RedisKey.STRATEGY_AWARD_COUNT_QUERY_KEY;
        RBlockingQueue<StrategyAwardStockKeyVO> blockingQueue = redissonService.getBlockingQueue(cacheKey);
        RDelayedQueue<StrategyAwardStockKeyVO> delayedQueue = redissonService.getDelayedQueue(blockingQueue);
        delayedQueue.offer(strategyAwardStockKeyVO, 3, TimeUnit.SECONDS);

    }

    /**
     * 消费阻塞队列消息
     * @return
     */
    @Override
    public StrategyAwardStockKeyVO takeQueueValue() {
        String cacheKey = Constants.RedisKey.STRATEGY_AWARD_COUNT_QUERY_KEY;
        RBlockingQueue<StrategyAwardStockKeyVO> blockingQueue = redissonService.getBlockingQueue(cacheKey);
        return blockingQueue.poll();
    }

    @Override
    public void updateStrategyAwardStock(Long strategyId, Integer awardId) {

        StrategyAward strategyAward = new StrategyAward();
        strategyAward.setStrategyId(strategyId);
        strategyAward.setAwardId(awardId);
        strategyAwardDao.updateStrategyAwardStock(strategyAward);
    }

    @Override
    public StrategyAwardEntity queryStrategyEntity(Long strategyId, Integer awardId) {
        String cacheKey = Constants.RedisKey.STRATEGY_AWARD_KEY + strategyId + Constants.UNDERLINE + awardId;

        StrategyAwardEntity strategyAwardEntity = redissonService.getValue(cacheKey);
        if (strategyAwardEntity != null) {
            return strategyAwardEntity;
        }

        StrategyAward strategyAwardReq = new StrategyAward();
        strategyAwardReq.setStrategyId(strategyId);
        strategyAwardReq.setAwardId(awardId);
        StrategyAward strategyAwardRes = strategyAwardDao.queryStrategyAward(strategyAwardReq);

        strategyAwardEntity = StrategyAwardEntity.builder()
                .strategyId(strategyAwardRes.getStrategyId())
                .awardId(strategyAwardRes.getAwardId())
                .awardCount(strategyAwardRes.getAwardCount())
                .awardCountSurplus(strategyAwardRes.getAwardCountSurplus())
                .awardRate(strategyAwardRes.getAwardRate())
                .awardTitle(strategyAwardRes.getAwardTitle())
                .awardSubtitle(strategyAwardRes.getAwardSubtitle())
                .sort(strategyAwardRes.getSort())
                .ruleModels(strategyAwardRes.getRuleModels())
                .build();
        redissonService.setValue(cacheKey, strategyAwardEntity);
        return strategyAwardEntity;
    }

    @Override
    public Long queryStrategyIdByActivityId(Long activityId) {

        return raffleActivityDao.queryStrategyIdByActivityId(activityId);
    }

    @Override
    public Integer queryTodayUserRaffleCount(String userId, Long strategyId) {
        Long activityId = raffleActivityDao.queryActivityIdByStrategyId(strategyId);
        RaffleActivityAccountDay raffleActivityAccountDayReq = new RaffleActivityAccountDay();
        raffleActivityAccountDayReq.setUserId(userId);
        raffleActivityAccountDayReq.setActivityId(activityId);
        raffleActivityAccountDayReq.setDay(raffleActivityAccountDayReq.curDay());
        RaffleActivityAccountDay raffleActivityAccountDay = raffleActivityAccountDayDao.queryActivityAccountDayByUserId(raffleActivityAccountDayReq);
        if (raffleActivityAccountDay == null) {
            return null;
        }
        return raffleActivityAccountDay.getDayCount() - raffleActivityAccountDay.getDayCountSurplus();


    }

    @Override
    public Map<String, Integer> queryAwardRuleLockCount(String[] treeIds) {
        if (treeIds == null || treeIds.length == 0) {
            return new HashMap<>();
        }
        List<RuleTreeNode> ruleTreeNodes = ruleTreeNodeDao.queryRuleLocks(treeIds);
        HashMap<String, Integer> resultMap = new HashMap<>();

        for (RuleTreeNode ruleTreeNode : ruleTreeNodes) {
            String treeId = ruleTreeNode.getTreeId();
            Integer count = Integer.parseInt(ruleTreeNode.getRuleValue());
            resultMap.put(treeId, count);

        }
        return resultMap;
    }

    @Override
    public List<RuleWeightVO> queryAwardRuleWeight(Long strategyId) {
        String cacheKey = Constants.RedisKey.STRATEGY_RULE_WEIGHT_KEY + strategyId;
        List<RuleWeightVO> ruleWeightVOS = redissonService.getValue(cacheKey);
        if (ruleWeightVOS != null) {
            return ruleWeightVOS;
        }
        ruleWeightVOS = new ArrayList<>();
        StrategyRule strategyRuleReq = new StrategyRule();
        strategyRuleReq.setStrategyId(strategyId);
        strategyRuleReq.setRuleModel(DefaultChainFactory.LogicModel.RULE_WEIGHT.getCode());

        String ruleValue = strategyRuleDao.queryStrategyRuleValue(strategyRuleReq);

        StrategyRuleEntity strategyRuleEntity = new StrategyRuleEntity();
        strategyRuleEntity.setRuleValue(ruleValue);
        strategyRuleEntity.setRuleModel(DefaultChainFactory.LogicModel.RULE_WEIGHT.getCode());
        Map<String, List<Integer>> ruleWeightValues = strategyRuleEntity.getRuleWeightValues();

        Set<String> ruleWeightKeys = ruleWeightValues.keySet();
        for (String ruleWeightKey : ruleWeightKeys) {
            List<Integer> awardIds = ruleWeightValues.get(ruleWeightKey);
            ArrayList<RuleWeightVO.Award> awards = new ArrayList<>();
            for (Integer awardId : awardIds) {
                StrategyAward strategyAwardReq = new StrategyAward();
                strategyAwardReq.setStrategyId(strategyId);
                strategyAwardReq.setAwardId(awardId);
                StrategyAward strategyAward = strategyAwardDao.queryStrategyAward(strategyAwardReq);
                awards.add(RuleWeightVO.Award.builder()
                        .awardId(strategyAward.getAwardId())
                        .awardTitle(strategyAward.getAwardTitle())
                        .build());
            }
            ruleWeightVOS.add(RuleWeightVO.builder()
                    .ruleValue(ruleValue)
                    .weight(Integer.valueOf(ruleWeightKey.split(Constants.COLON)[0]))
                    .awardIds(awardIds)
                    .awardList(awards)
                    .build());

        }
        redissonService.setValue(cacheKey, ruleWeightVOS);

        return ruleWeightVOS;


    }

    @Override
    public Integer queryActivityAccountTotalUseCount(String userId, Long strategyId) {
        Long activityId = raffleActivityDao.queryActivityIdByStrategyId(strategyId);
        RaffleActivityAccount activityAccount =
                raffleActivityAccountDao.queryActivityAccountByUserId(RaffleActivityAccount.builder().userId(userId).activityId(activityId).build());
        //活动配置的总次数-可用剩余次数即用户已经抽奖次数
        return activityAccount.getTotalCount() - activityAccount.getTotalCountSurplus();
    }
}
